package demo.utils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import demo.vo.AlertLogClassifyRuleDO;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

/**
 * 日志归类工具类。通过logDetail或logIdentity匹配关键字将日志归类。
 * <p>
 *
 * @author junying.han
 */
public class LogClassifyUtil {

    public static final String NGINX_LOG_APP_NAME = "nginx-log";
    public static final String SPLIT_CHAR = ":";

    private LogClassifyUtil() {
        super();
    }

    private static Logger logger = LoggerFactory.getLogger(LogClassifyUtil.class);

    public static final String KEY_RULE_LOG_IDENTITY = "KEY_RULE_LOG_IDENTITY";
    public static final String KEY_RULE_LOG_DETAIL = "KEY_RULE_LOG_DETAIL";

    /**
     * 1小时的缓存：因为规则变化很少变化，不需要频繁更新。
     */
    private static Cache<String, Map<String, String>> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(6, TimeUnit.HOURS).maximumSize(10).build();

    /**
     * 从缓存获取规则
     *
     * @param key KEY_RULE_LOG_IDENTITY/KEY_RULE_LOG_DETAIL
     * @return
     */
    public static Map<String, String> loadRulesFromCache(String key) {
        Map<String, String> rules = cache.getIfPresent(key);
        if (rules == null) {
            try {
                List<AlertLogClassifyRuleDO> all = loadRuleFromXdcs();
                logger.warn("loadRuleFromXdcs return {} records.", all.size());
                Map<String, String> logIdentityRules = new HashMap<String, String>();
                Map<String, String> logDetailRules = new HashMap<String, String>();
                for (AlertLogClassifyRuleDO vo : all) {
                    if (vo.getKeywordType() == null) {
                        continue;
                    }
                    if (vo.getKeywordType().intValue() == 1) {
                        logIdentityRules.put(vo.getLogKeyword(), vo.getClassify());
                    }
                    if (vo.getKeywordType().intValue() == 2) {
                        logDetailRules.put(vo.getLogKeyword(), vo.getClassify());
                    }
                }
                cache.put(KEY_RULE_LOG_DETAIL, logDetailRules);
                cache.put(KEY_RULE_LOG_IDENTITY, logIdentityRules);
            } catch (Exception e) {
                logger.error("loadRuleFromXdcs failed.", e);
                cache.put(KEY_RULE_LOG_DETAIL, LOG_DETAIL_CLASSIFY_RULE);
                cache.put(KEY_RULE_LOG_IDENTITY, LOG_IDENTITY_CLASSIFY_RULE);
            }
        }
        return cache.getIfPresent(key);
    }

    /**
     * 优先通过 logDetail 匹配，没有匹配到再 logIdentity匹配
     *
     * @param logIdentity
     * @param logDetail
     * @return
     */
    public static String logType(String logIdentity, String logDetail) {
        String classifyType = logTypeByDetail(logDetail);
        if (classifyType == null) {
            classifyType = logTypeByIdentity(logIdentity);
        }
        return classifyType == null ? XdcsSparkConstant.TYPE_OTHER : classifyType;
    }

    public static String logNginxType(String logIdentity) {
        try {
            if (StringUtils.isEmpty(logIdentity)) {
                return XdcsSparkConstant.TYPE_OTHER;
            }
            //api.ximalaya.com:4xx
            String[] arr = StringUtils.split(logIdentity, SPLIT_CHAR);
            if (arr.length != 2) {
                return XdcsSparkConstant.TYPE_OTHER;
            }
            return arr[1];
        } catch (Exception e) {
            return XdcsSparkConstant.TYPE_OTHER;
        }
    }

    /**
     * 优先通过 logDetail 匹配，没有匹配到再 logIdentity匹配
     *
     * @param logIdentity
     * @param logDetail
     * @return
     */
    public static String logTypeStatic(String logIdentity, String logDetail) {
        String matchRule = logTypeByDetailStatic(logDetail);
        if (matchRule == null) {
            matchRule = logTypeByIdentityStatic(logIdentity);
        }
        if (matchRule == null) {
            return XdcsSparkConstant.TYPE_OTHER;
        }
        return matchRule;
    }

    /**
     * 通过 logDetail 判断错误归类
     *
     * @param logDetail
     * @return
     */
    public static String logTypeByDetail(String logDetail) {
        if (logDetail == null || logDetail.isEmpty()) {
            return null;
        }
        Map<String, String> rules = loadRulesFromCache(KEY_RULE_LOG_DETAIL);
        if (rules == null || rules.isEmpty()) {
            return null;
        }
        for (Entry<String, String> entry : rules.entrySet()) {
            String keyWord = entry.getKey();
            String type = entry.getValue();
            if (logDetail.contains(keyWord)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 通过 logIdentity 判断错误归类
     *
     * @param logDetail
     * @return
     */
    public static String logTypeByDetailStatic(String logDetail) {
        if (logDetail == null || logDetail.isEmpty()) {
            return null;
        }
        for (Entry<String, String> entry : LOG_DETAIL_CLASSIFY_RULE.entrySet()) {
            String keyWord = entry.getKey();
            String type = entry.getValue();
            if (logDetail.contains(keyWord)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 通过 logIdentity 判断错误归类
     *
     * @param logIdentity
     * @return
     */
    public static String logTypeByIdentity(String logIdentity) {
        if (logIdentity == null || logIdentity.isEmpty()) {
            return null;
        }
        Map<String, String> rules = loadRulesFromCache(KEY_RULE_LOG_IDENTITY);
        if (rules == null || rules.isEmpty()) {
            return null;
        }
        for (Entry<String, String> entry : rules.entrySet()) {
            String keyWord = entry.getKey();
            String type = entry.getValue();
            if (logIdentity.contains(keyWord)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 通过 logIdentity 判断错误归类
     *
     * @param logIdentity
     * @return
     */
    public static String logTypeByIdentityStatic(String logIdentity) {
        if (logIdentity == null || logIdentity.isEmpty()) {
            return null;
        }
        for (Entry<String, String> entry : LOG_IDENTITY_CLASSIFY_RULE.entrySet()) {
            String keyWord = entry.getKey();
            String type = entry.getValue();
            if (logIdentity.contains(keyWord)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 通过XDCS-HOME接口获取归类规则
     *
     * @return
     */
    public static List<AlertLogClassifyRuleDO> loadRuleFromXdcs() {
        return Collections.emptyList();
    }


    /**
     * logDetail规则，KEY ： 关键字， value： 归类
     */
    private static final Map<String, String> LOG_IDENTITY_CLASSIFY_RULE = new HashMap<String, String>();
    /**
     * logIdentity规则，KEY ： 关键字， value： 归类
     */
    private static final Map<String, String> LOG_DETAIL_CLASSIFY_RULE = new HashMap<String, String>();

    static {
        // rpcTimeout
        LOG_DETAIL_CLASSIFY_RULE.put("MainstayTimeoutException", "rpcTimeout");
        // 空指针
        LOG_DETAIL_CLASSIFY_RULE.put("NullPointerException", "NPE");
        // SQL
        LOG_DETAIL_CLASSIFY_RULE.put("BadSqlGrammarException", "sql");

        // DB
        LOG_IDENTITY_CLASSIFY_RULE.put(".jdbc.", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put(".jdbc4.", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put("executeSql", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put("executeQuery", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put(".mybatis.", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put(".ibatis.", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put("SQLException", XdcsSparkConstant.TYPE_DB);
        LOG_IDENTITY_CLASSIFY_RULE.put("DbException", XdcsSparkConstant.TYPE_DB);

        // RabbitMQ
        LOG_IDENTITY_CLASSIFY_RULE.put("org.springframework.amqp.rabbit", XdcsSparkConstant.TYPE_RABBITMQ);
        LOG_IDENTITY_CLASSIFY_RULE.put("com.rabbitmq.client", XdcsSparkConstant.TYPE_RABBITMQ);

        // REDIS
        LOG_IDENTITY_CLASSIFY_RULE.put("redis.clients.", XdcsSparkConstant.TYPE_REDIS);
        LOG_IDENTITY_CLASSIFY_RULE.put("org.springframework.data.redis", XdcsSparkConstant.TYPE_REDIS);
        LOG_IDENTITY_CLASSIFY_RULE.put("com.ximalaya.shardis.jedis.", XdcsSparkConstant.TYPE_REDIS);
        LOG_IDENTITY_CLASSIFY_RULE.put("JedisException", XdcsSparkConstant.TYPE_REDIS);
        LOG_IDENTITY_CLASSIFY_RULE.put("RedisException", XdcsSparkConstant.TYPE_REDIS);

        // ZK
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.curator.", XdcsSparkConstant.TYPE_ZK);
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.zookeeper.", XdcsSparkConstant.TYPE_ZK);

        // OOM
        LOG_IDENTITY_CLASSIFY_RULE.put("StackOverflowError", XdcsSparkConstant.TYPE_OOM);
        LOG_IDENTITY_CLASSIFY_RULE.put("OutOfMemoryError", XdcsSparkConstant.TYPE_OOM);

        // mainstay
        LOG_IDENTITY_CLASSIFY_RULE.put("com.ximalaya.mainstay.", XdcsSparkConstant.TYPE_MAINSTAY);
        LOG_IDENTITY_CLASSIFY_RULE.put("com.ximalaya.thrift.", XdcsSparkConstant.TYPE_MAINSTAY);
        LOG_IDENTITY_CLASSIFY_RULE.put("com.ximalaya.nacos.", XdcsSparkConstant.TYPE_MAINSTAY);
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.thrift.", XdcsSparkConstant.TYPE_MAINSTAY);

        // KAFKA
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.kafka", XdcsSparkConstant.TYPE_KAFKA);
        LOG_IDENTITY_CLASSIFY_RULE.put(".danube.", XdcsSparkConstant.TYPE_KAFKA);
        LOG_IDENTITY_CLASSIFY_RULE.put("kafka.producer", XdcsSparkConstant.TYPE_KAFKA);
        LOG_IDENTITY_CLASSIFY_RULE.put("kafka.consumer", XdcsSparkConstant.TYPE_KAFKA);
        LOG_IDENTITY_CLASSIFY_RULE.put("kafka.utils.", XdcsSparkConstant.TYPE_KAFKA);

        // ES
        LOG_IDENTITY_CLASSIFY_RULE.put(".elasticsearch.", "es");

        // IGNITE
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.ignite", "ignite");

        // HBASE
        LOG_IDENTITY_CLASSIFY_RULE.put(".hbase.", "hbase");
        LOG_IDENTITY_CLASSIFY_RULE.put("OpenTsdb", "hbase");

        // HDFS
        LOG_IDENTITY_CLASSIFY_RULE.put(".hdfs.", "hdfs");
        LOG_IDENTITY_CLASSIFY_RULE.put(".hadoop.fs.", "hdfs");

        // HTTP
        LOG_IDENTITY_CLASSIFY_RULE.put("httpRequest", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.http.", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put(".http11.", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("HttpSolrClient", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("HttpConnection", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("Inet6AddressImpl", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("okhttp", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("java.net.URI", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("httpClient", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("RestTemplate", "http");
        LOG_IDENTITY_CLASSIFY_RULE.put("UnknownHostException", "http");

        // XDCS
        LOG_IDENTITY_CLASSIFY_RULE.put("com.ximalaya.xdcs.client", "xdcs");

        // C++
        LOG_IDENTITY_CLASSIFY_RULE.put(".cpp:", "cpp");

        // Ruby
        LOG_IDENTITY_CLASSIFY_RULE.put("/ruby/", "ruby");

        // TOMCAT
        LOG_IDENTITY_CLASSIFY_RULE.put("org.apache.catalina", "tomcat");

        // 空指针
        LOG_IDENTITY_CLASSIFY_RULE.put("NullPointerException", "NPE");

    }
}
